/*
 * Copyright 2013 JROSE
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jrose.service.webapp.exposer;

import lombok.NoArgsConstructor;
import lombok.Setter;
import org.jrose.api.annotation.remote.ExposeHttpService;
import org.jrose.core.util.ApplicationContextProvider;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

/**
 * Automatically expose http service.
 *
 * @author Xiaohai Zhang
 * @since 2013-08-20 15:38
 */
@NoArgsConstructor
public class HttpServiceAutoExposer extends ApplicationContextProvider implements BeanFactoryPostProcessor, BeanClassLoaderAware {
    @Setter private ClassLoader beanClassLoader;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (beanFactory.getParentBeanFactory() == null) {
            log.warn("No parent bean factory found, ignore.");
            return;
        }
        if (!(beanFactory.getParentBeanFactory() instanceof ConfigurableListableBeanFactory)) {
            log.warn("Parent bean factory is not ConfigurableListableBeanFactory, ignore.");
            return;
        }

        ConfigurableListableBeanFactory parent = (ConfigurableListableBeanFactory) beanFactory.getParentBeanFactory();
        for (String beanName : parent.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition;
            try {
                beanDefinition = parent.getBeanDefinition(beanName);
            } catch (NoSuchBeanDefinitionException ex) {
                log.warn("No bean with name '{}' found, ignore.", beanName);
                continue;
            }

            if (beanDefinition.getBeanClassName() == null && beanDefinition.getFactoryBeanName() != null) {
                log.warn("Bean {} is builded from factory {}, ignore.", beanName, beanDefinition.getFactoryBeanName());
                continue;
            }

            // try to load bean class
            Class<?> beanClass;
            try {
                beanClass = beanClassLoader.loadClass(beanDefinition.getBeanClassName());
            } catch (ClassNotFoundException ex) {
                log.warn("Cannot load bean class {}, ignore.", beanDefinition.getBeanClassName());
                continue;
            }

            if (!beanClass.isAnnotationPresent(ExposeHttpService.class)) {
                // No @ExposeHttpService presented, ignore.
                continue;
            }

            exposeHttpService(beanFactory, beanName, beanClass, beanClass.getAnnotation(ExposeHttpService.class));
        }
    }

    private void exposeHttpService(ConfigurableListableBeanFactory beanFactory,
                                   String beanName,
                                   Class<?> beanClass,
                                   ExposeHttpService annotation) {
        if (!annotation.serviceInterface().isAssignableFrom(beanClass)) {
            throw new RuntimeException("Bean " + beanClass + " doesn't implement interface " + annotation.serviceInterface().getName());
        }
        ExtendedHttpInvokerServiceExporter exporter = new ExtendedHttpInvokerServiceExporter();
        exporter.setBeanClassLoader(beanClassLoader);
        exporter.setService(applicationContext.getBean(beanName));
        exporter.setServiceInterface(annotation.serviceInterface());
        exporter.afterPropertiesSet();

        if (applicationContext.containsBean(annotation.name())) {
            throw new RuntimeException("Bean name " + annotation.name() + " already exists");
        }
        beanFactory.registerSingleton(annotation.name(), exporter);

        LoggerFactory.getLogger("expose.service").info("Exposed '{}'", annotation.serviceInterface().getName());
    }
}
